/*
 * Copyright 2012 David Crosson
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package fr.janalyse.jmx

import scala.util.{ Properties => SP }
import java.io.File.{ separator => FS, pathSeparator => PS }
import scala.reflect.BeanProperty
import java.lang.management.ManagementFactory
import javax.management.ObjectName
import javax.management.remote.rmi.RMIConnection
import javax.management.remote.rmi.RMIServer
import javax.management.remote.rmi.RMIServerImpl_Stub
import javax.management.openmbean.{ CompositeData, CompositeDataSupport }
import javax.management.openmbean.{ TabularData, TabularDataSupport }
import java.rmi.MarshalledObject
import java.rmi.UnmarshalException
import javax.management.openmbean.CompositeData
import scala.collection.JavaConversions._
import javax.management.MBeanAttributeInfo
import java.rmi.registry.LocateRegistry
import javax.management.remote.JMXConnector
import javax.management.remote.JMXServiceURL
import javax.management.remote.{ JMXConnectorServerFactory, JMXConnectorFactory }
import java.rmi.server.RMIClientSocketFactory
import javax.management.remote.rmi.RMIJRMPServerImpl
import java.rmi.server.RMIServerSocketFactory
import javax.net.ServerSocketFactory
import java.net.ServerSocket
import java.rmi.server.RMISocketFactory
import javax.management.RuntimeMBeanException
import scala.math.ScalaNumber
import javax.management.MBeanInfo
import javax.management.MBeanServerConnection
import javax.management.remote.JMXConnectorServer

case class CompositeDataWrapper[CDT <: CompositeData](cd: CDT) {
  lazy val content: Map[String, Object] = {
    val keys = cd.getCompositeType().keySet()
    val tuples = keys.toList map { k => (k -> cd.get(k)) }
    tuples.toMap
  }
  def toWrapper() = this
  //def get(key:String):Option[Object] = content.get(key)
  def getString(key: String): Option[String] = content.get(key) map { _.toString }
  def get[A](key: String): Option[A] = content.get(key) map { _.asInstanceOf[A] }

  // TODO Improve number support and add more typed getter for basic types
  def getNumber[N >: Number](key: String): Option[N] = {
    for (raw <- content.get(key)) yield {
      raw match {
        case e: java.lang.Byte => e.toDouble
        case e: java.lang.Short => e.toDouble
        case e: java.lang.Integer => e.toDouble
        case e: java.lang.Long => e.toDouble
        case e: java.lang.Float => e.toDouble
        case e: java.lang.Double => e
        case e: java.lang.String => e.toDouble
      }
    }
  }
}

case class TabularDataWrapper[TD <: TabularData](tabularData: TD) {
  lazy val content: Map[String, Map[String, Object]] = {
    val indexNamesInCell = tabularData.getTabularType().getIndexNames()
    val tuples = tabularData.values collect {
      case v: CompositeDataSupport =>
        val cellContent = v.content
        val key = indexNamesInCell map { cellContent.get(_).get } mkString ("-")
        val submap = cellContent filterNot { case (k, _) => indexNamesInCell contains k }

        key -> submap

      case (k: java.util.List[String], v: CompositeData) =>
        val cellContent = v.content
        val key = indexNamesInCell map { cellContent.get(_).get } mkString ("-")
        val submap = cellContent filterNot { case (k, _) => indexNamesInCell contains k }

        key -> submap
    }
    tuples.toMap
  }
  def toWrapper() = this
  def get(key: String): Option[Map[String, Object]] = content.get(key)
  def get(key: String, subkey: String): Option[Object] = content.get(key) flatMap { _.get(subkey) }
  def getString(key: String, subkey: String): Option[String] = get(key, subkey) map { _.toString }
}

case class RichMBean(
  objectName: ObjectName,
  mbeanInfoGetter: () => MBeanInfo,
  attributeGetter: (String) => Option[Object],
  attributeSetter: (String, Any) => Unit,
  operationCaller: (String, Array[Any]) => Option[Any]) {
  
  lazy val mbeanInfo = mbeanInfoGetter()
  val name = objectName.toString()
  val domain = objectName.getDomain()
  val keys = mapAsScalaMap(objectName.getKeyPropertyList())
  
  lazy val attributesMap: Map[String, RichAttribute] = {
    try {
      val attrsInfo: List[MBeanAttributeInfo] = mbeanInfo.getAttributes().toList
      attrsInfo map { ai =>
        val n = ai.getName()
        val d = if (ai.getDescription() == ai.getName || ai.getDescription().trim.size == 0) None else Some(ai.getDescription())
        val a: RichAttribute = ai.getType() match {
          case "java.lang.Boolean" | "boolean" | "java.lang.boolean" => RichBooleanAttribute(n, d)
          case "java.lang.Byte" | "byte" => RichByteAttribute(n, d)
          case "java.lang.Short" | "short" => RichShortAttribute(n, d)
          case "java.lang.Integer" | "int" => RichIntAttribute(n, d)
          case "java.lang.Long" | "long" => RichLongAttribute(n, d)
          case "java.lang.Float" | "float" => RichFloatAttribute(n, d)
          case "java.lang.Double" | "double" => RichDoubleAttribute(n, d)
          case "java.lang.String" | "String" => RichStringAttribute(n, d)
          //case "java.util.List" =>
          //case "java.util.Properties" =>
          case "[Ljava.lang.String;" => RichStringArrayAttribute(n, d)
          //case "javax.management.openmbean.TabularData"      =>
          case "javax.management.openmbean.CompositeData" | "javax.management.openmbean.CompositeDataSupport" => RichCompositeDataAttribute(n, d)
          //case "[Ljavax.management.openmbean.CompositeData;" =>
          //case "[Ljavax.management.ObjectName;" => 
          case x =>
            //println("Warning: Not supported jmx attribute value type %s for %s mbean %s".format(x, n, name))
            //println(x)
            RichGenericAttribute(ai.getName(), d)
        }
        n -> a
      } toMap
      //def apply[A](attrname:String) = jmx.rmiConnection.getAttribute(objectName,attrname,null).asInstanceOf[A]
    } catch {
      case e =>
        println("Couln't build attributes map for MBean %s (%s)".format(name, e.getMessage))
        Map.empty[String, RichAttribute]
    }
  }

  def get[A](name: String): Option[A] = {
    val attrsInfo: List[javax.management.MBeanAttributeInfo] = mbeanInfo.getAttributes().toList
    (attrsInfo find { attrInfo => attrInfo.getName == name }) match {
      case None => None
      case Some(attrInfo) => apply[A](name) match {
        case x if (x == null) => None
        case x => Some(x)
      }
    }
  }
  def apply[A](attrname: String) = attributeGetter(attrname).map(_.asInstanceOf[A]).get
  def set(attrname: String, value: Any) { attributeSetter(attrname, value) }
  def apply[A](attrnames: String*): List[A] = attrnames.toList map { apply[A](_) }
  def call[A](operation: String, args: Any*): Option[A] = operationCaller(operation, args.toArray[Any]).map(_.asInstanceOf[A])

  private def genericGetter[T, R <: RichAttribute](attr: R, getter: (Object) => T): Option[T] = {
    try {
      attributeGetter(attr.name).map(getter(_))
    } catch {
      case e: RuntimeMBeanException if e.getCause().isInstanceOf[UnsupportedOperationException] => None
      case e: UnmarshalException => None
      case e: java.rmi.ConnectException => throw e
      case x =>
        //println("Warning: Error while getting value for attribute %s mbean %s (%s)".format(attr.name, name, x))
        None
    }
  }

  def getString(attr: RichAttribute): Option[String] = genericGetter(attr, attr.asString)

  def getDouble(attr: RichNumberAttribute): Option[Double] = genericGetter(attr, attr.asDouble)

  def getLong(attr: RichNumberAttribute): Option[Long] = genericGetter(attr, attr.asLong)

  def getInt(attr: RichNumberAttribute): Option[Int] = genericGetter(attr, attr.asInt)

  def getComposite(attr: RichCompositeDataAttribute): Option[Map[String, Object]] = genericGetter(attr, attr.asMap)

  def getNumberComposite[N >: Number](attr: RichCompositeDataAttribute): Option[Map[String, N]] = genericGetter(attr, attr.asNumberMap[N])

  def getString(attrname: String): Option[String] = attributesMap.get(attrname).flatMap(getString(_))

  def getDouble(attrname: String): Option[Double] = attributesMap.get(attrname).collect({ case x: RichNumberAttribute => x }).flatMap(getDouble(_))

  def getLong(attrname: String): Option[Long] = attributesMap.get(attrname).collect({ case x: RichNumberAttribute => x }).flatMap(getLong(_))

  def getInt(attrname: String): Option[Int] = attributesMap.get(attrname).collect({ case x: RichNumberAttribute => x }).flatMap(getInt(_))

  def getComposite(attrname: String): Option[Map[String, Object]] = attributesMap.get(attrname).collect({ case x: RichCompositeDataAttribute => x }).flatMap(getComposite(_))

  def getNumberComposite[N >: Number](attrname: String): Option[Map[String, N]] = attributesMap.get(attrname).collect({ case x: RichCompositeDataAttribute => x }).flatMap(getNumberComposite(_))

  def attributes(): List[RichAttribute] = attributesMap.values.toList
  
  def attributesNames() : List[String] = attributesMap.values.toList.map(_.name)
}

trait RichAttribute {
  val name: String
  val desc: Option[String]
  def asString(ob: Object): String = ob.toString() // TODO BAD
}

trait RichArrayAttribute extends RichAttribute {
}

trait RichNumberAttribute extends RichAttribute {
  def asDouble(ob: Object): Double = ob match { // TODO BAD
    case e: java.lang.Byte => e.toDouble
    case e: java.lang.Short => e.toDouble
    case e: java.lang.Integer => e.toDouble
    case e: java.lang.Long => e.toDouble
    case e: java.lang.Float => e.toDouble
    case e: java.lang.Double => e
    case e: java.lang.String => e.toDouble
  }
  def asLong(ob: Object): Long = ob match { // TODO BAD
    case e: java.lang.Byte => e.toLong
    case e: java.lang.Short => e.toLong
    case e: java.lang.Integer => e.toLong
    case e: java.lang.Long => e
    case e: java.lang.Float => e.toLong
    case e: java.lang.Double => e.toLong
    case e: java.lang.String => e.toLong
  }
  def asInt(ob: Object): Int = ob match { // TODO BAD
    case e: java.lang.Byte => e.toInt
    case e: java.lang.Short => e.toInt
    case e: java.lang.Integer => e
    case e: java.lang.Long => e.toInt
    case e: java.lang.Float => e.toInt
    case e: java.lang.Double => e.toInt
    case e: java.lang.String => e.toInt
  }

}

case class RichCompositeDataAttribute(name: String, desc: Option[String] = None) extends RichAttribute {
  override def asString(ob: Object): String = {
    val cd = ob.asInstanceOf[CompositeData] // TODO BAD
    cd.toString()
  }
  def asMap(ob: Object) = ob.asInstanceOf[CompositeData].content // TODO BAD
  def asNumberMap[N >: Number](ob: Object): Map[String, N] = {
    val cd = ob.asInstanceOf[CompositeData] // TODO BAD
    cd.content flatMap {
      case (name, value) =>
        value match {
          case x: java.lang.Long => Some(name -> new runtime.RichDouble(x.toDouble))
          case x: java.lang.Double => Some(name -> new runtime.RichDouble(x.toDouble))
          case x: java.lang.Integer => Some(name -> new runtime.RichDouble(x.toDouble))
        }
    }
  }
}

case class RichStringArrayAttribute(name: String, desc: Option[String] = None) extends RichArrayAttribute {
  override def asString(ob: Object): String = { // TODO BAD
    ob.asInstanceOf[Array[String]].toList.mkString(", ")
  }
}

case class RichBooleanAttribute(name: String, desc: Option[String] = None) extends RichAttribute

case class RichStringAttribute(name: String, desc: Option[String] = None) extends RichAttribute

case class RichGenericAttribute(name: String, desc: Option[String] = None) extends RichAttribute

case class RichByteAttribute(name: String, desc: Option[String] = None) extends RichNumberAttribute
case class RichShortAttribute(name: String, desc: Option[String] = None) extends RichNumberAttribute
case class RichIntAttribute(name: String, desc: Option[String] = None) extends RichNumberAttribute
case class RichLongAttribute(name: String, desc: Option[String] = None) extends RichNumberAttribute
case class RichDoubleAttribute(name: String, desc: Option[String] = None) extends RichNumberAttribute
case class RichFloatAttribute(name: String, desc: Option[String] = None) extends RichNumberAttribute

case class Credentials(username: String = "", password: String = "")

case class JMXOptions(
  host: String = "localhost",
  port: Int = 1099,
  url: Option[JMXServiceURL] = None,
  name: Option[String] = None,
  username: Option[String] = None,
  password: Option[String] = None,
  connectTimeout: Long = 30000,
  retryCount: Int = 5,
  retryDelay: Long = 2000) {
  val credentials: Option[Credentials] = (username, password) match {
    case (None, None) => None
    case (None, _) => None
    case (Some(u), Some(p)) => Some(Credentials(u, p))
  }
}

object JMXOptions {
  def apply(url: JMXServiceURL): JMXOptions = 
    new JMXOptions(url.getHost, url.getPort, url = Some(url))

  def apply(url: JMXServiceURL, username: String, password: String): JMXOptions = 
    new JMXOptions(url.getHost, url.getPort, url = Some(url), username = Some(username), password = Some(password))
  
}

trait ASSignature {
  val release: String
  val home: String
  val domain: String // jmx domain where to find all mbeans or "".
  val family: String // AS family name : jboss, tomcat, jetty, jonas, ...
}

case class TomcatSignature(
  release: String, // tomcat release
  home: String, // tomcat home directory (CATALINA_HOME)
  domain: String = "Catalina" // tomcat JMX Domain
  ) extends ASSignature  {
  val family = "tomcat"
}

case class JettySignature(
  release: String,
  home: String,
  domain: String = ""
) extends ASSignature {
  val family = "jetty"
}
  
case class WebMethodsSignature(
  release: String, // Webmethod release
  home: String, // Webmethod home directory (WM_HOME)
  domain: String = "" // Not yet used
  // com.webmethods.sc.config.configRoot
  ) extends ASSignature  {
  val family = "webmethods"
}

case class JonasSignature(
  release: String, // jonas release
  domain: String, // jonas JMX domain name
  name: String, // jonas Instance name
  home: String, // jonasBase jmx value or jonas.base property value
  root: String // jonasRoot jmx value or jonas.root property value
  ) extends ASSignature  {
  val family = "jonas"
}

case class JBossSignature(
  release: String, // jboss release
  home: String, // jboss home directory (WM_HOME)
  domain: String = "jboss.as" // jboss.as
  ) extends ASSignature {
  val family = "jboss"
}

  
object ASSignature {
  private def getJonasDomain(jmx: JMX) = jmx.domains find { domain =>
    !jmx.mbeans("%s:j2eeType=J2EEServer,*".format(domain)).isEmpty
  }
  private def getJonasName(jmx: JMX, domain: String): Option[String] =
    jmx.mbeans("%s:j2eeType=J2EEServer,*".format(domain)).flatMap(_.keys.get("name")).headOption

  private def lookup4jonasSignature(jmx: JMX):Option[ASSignature] = {
    for (
      domain <- getJonasDomain(jmx);
      name <- getJonasName(jmx, domain);
      j2eeserver <- jmx.get("%s:j2eeType=J2EEServer,name=%s".format(domain, name));
      asname <- j2eeserver.getString("serverName");
      rel <- j2eeserver.getString("serverVersion")
    ) yield {
      lazy val basedirFromEngine = jmx.get("%s:type=Engine".format(domain)) flatMap { _.getString("baseDir") }
      lazy val basedirFromProps = jmx.systemProperties.get("jonas.base")
      val jonasBase = j2eeserver.getString("jonasBase") getOrElse (basedirFromEngine getOrElse basedirFromProps.get) // TODO not 100% safe
      lazy val rootdirFromProps = jmx.systemProperties.get("jonas.root") orElse jmx.systemProperties.get("install.root")
      val jonasRoot = j2eeserver.getString("jonasRoot") getOrElse rootdirFromProps.get // TODO not 100% safe

      JonasSignature(rel, domain, name, jonasBase, jonasRoot)
    }
  }

  private def lookup4jettySignature(jmx:JMX):Option[ASSignature] = {
    val jettydomains = jmx.domains.filter(_.contains("jetty"))
    if (jettydomains.isEmpty) None else {
      val mbs = jettydomains.flatMap(jd => jmx.mbeans(jd + ":*"))
      for (mb      <- mbs.find(_.attributesNames.contains("version")) ; 
           version <- mb.getString("version") ; 
           home    <- jmx.systemProperties.get("server.home") ;
           name    <- jmx.systemProperties.get("server.name") ) yield {
        JettySignature(release=version, home=home, domain=mb.domain)
      }
    }
  }
  
  private def lookup4webmethodsSignature(jmx: JMX) = {
    val wmIsBaseDirOption = (jmx.systemProperties.get("WM_HOME") map { _ + "/IntegrationServer" }) orElse {
      jmx.get("WmTomcat:type=Engine") map { engine =>
        engine[String]("baseDir").split("/").init.mkString("/")
      }
    }
    for (
      wmhome <- wmIsBaseDirOption
    ) yield {
      val isjars = jmx.systemProperties.get("wm.server.class.path") map { _.split(":").toList filter { _.contains("IS") } }
      val relsfromjar = isjars map { _.map(_.split("/").last.replace("IS_", "").replace(".jar", "").replace("-", ".")) }
      val rel = relsfromjar map { _.head }

      WebMethodsSignature(rel getOrElse "unknown", wmhome)
    }
  }

  private def lookup4jbossSignature(jmx: JMX) = {
    // At least ok with 7.1.x; TODO check with older releases
    for (
      jbossas <- jmx.get("jboss.as:management-root=server");
      release <- jbossas.getString("releaseVersion");
      core <- jmx.get("jboss.as:core-service=server-environment");
      baseDir <- core.getString("baseDir")
    ) yield {
      JBossSignature(
        release = release,
        home = baseDir
      )
    }
  }

  private def lookup4tomcatSignature(jmx: JMX) = {
    for (
      catalina <- jmx.get("Catalina:type=Server");
      engine <- jmx.get("Catalina:type=Engine");
      serverInfo <- catalina.getString("serverInfo");
      Array(id, rel) <- Option(serverInfo.split("/", 2))
    ) yield {
      lazy val basedirFromProps = jmx.systemProperties.get("catalina.home")
      val basedir = engine.getString("baseDir") getOrElse basedirFromProps.get
      TomcatSignature(rel, basedir)
    }
  }

  def whoami(jmx: JMX): Option[ASSignature] = {
    // The order is important, most specific to the most general.
    // (for example tomat is sometimes embedded into other application servers)
    val searchOrder: Stream[Option[ASSignature]] = Stream(
      lookup4jbossSignature(jmx),
      lookup4jettySignature(jmx),
      lookup4jonasSignature(jmx),
      lookup4webmethodsSignature(jmx),
      lookup4tomcatSignature(jmx)
    )
    searchOrder.find(_.isDefined).map(_.get)
  }
}

case class StackEntry(
  className: String,
  methodName: String,
  nativeMethod: Boolean,
  fileName: String,
  lineNumber: Int) {
  val id = "%s.%s".format(className, methodName)
  val pkg = className.split("[.]").init.mkString(".")
  def detailed = "%s (%s:%s)".format(id, if (fileName != null) fileName else "?", if (lineNumber > 0) lineNumber.toString else "?")
  override def toString = id
}

case class Lock(
  name: String,
  ownerId: Option[Long],
  ownerName: Option[String],
  serviceName: String)

case class ThreadInfo(
  name: String,
  id: Long,
  status: String,
  stack: List[StackEntry],
  lock: Option[Lock],
  blockedCount: Long,
  waitedCount: Long,
  serviceName: String)

object ThreadInfo {
  def apply(serviceName: String, ti: CompositeData) = {
    val id = ti.get("threadId").toString.toLong
    val name = ti.get("threadName").toString
    val state = ti.get("threadState").toString
    val blockedCount = ti.get("blockedCount").toString.toLong
    val waitedCount = ti.get("waitedCount").toString.toLong
    val stack = ti.get("stackTrace").asInstanceOf[Array[CompositeData]] map { stackitem =>
      val className = stackitem.get("className").asInstanceOf[String]
      val fileName = stackitem.get("fileName").asInstanceOf[String]
      val lineNumber = stackitem.get("lineNumber").asInstanceOf[Int]
      val methodName = stackitem.get("methodName").asInstanceOf[String]
      val nativeMethod = stackitem.get("nativeMethod").asInstanceOf[Boolean]
      StackEntry(className = className, fileName = fileName, lineNumber = lineNumber, methodName = methodName, nativeMethod = nativeMethod)
    }
    val lock = Option(ti.get("lockName")) map { _.toString } filter { _.size > 0 } map { lockName =>
      val lockOwnerId = Option(ti.get("lockOwnerId")) map { _.asInstanceOf[Long] } filterNot { _ == -1 }
      val lockOwnerName = Option(ti.get("lockOwnerName")) map { _.toString.trim }
      Lock(lockName, lockOwnerId, lockOwnerName, serviceName)
    }
    new ThreadInfo(name, id, state, stack.toList, lock, blockedCount, waitedCount, serviceName)
  }
}

case class ServiceThreads(
  serviceName: String,
  threads: Iterable[ThreadInfo]) {
  val locks = threads.flatMap(_.lock)
  val activeLocks = locks filter { _.ownerName.isDefined }
}

trait JMX {
  val options: Option[JMXOptions]
  def close()
  def exists(name: String): Boolean
  def domains: List[String]
  def mbeans(query: String): List[RichMBean]
  def mbeans(): List[RichMBean]
  def apply(name: String): RichMBean
  def get(name: String): Option[RichMBean] = if (exists(name)) Some(apply(name)) else None

  def whoami(): Option[ASSignature] = ASSignature.whoami(this)

  def os() = get("java.lang:type=OperatingSystem")
  def runtime() = get("java.lang:type=Runtime")
  def threading() = get("java.lang:type=Threading")
  def memory() = get("java.lang:type=Memory")
  def classLoading() = get("java.lang:type=ClassLoading")
  def compilation() = get("java.lang:type=Compilation")

  def gcforce() { memory.map(_.call("gc")) }

  // Service name introduced in order to mixup threads dumps, and allow data processing (eg groupBy)
  val serviceName = options.flatMap(_.name) getOrElse "default"

  def threadsDump(stackMaxDepth: Int = 9999): Option[ServiceThreads] = {
    javaSpecificationVersion match {
      case Some("1.5") => incrementalThreadDump(stackMaxDepth)
      case _ => simultaneousThreadDump(stackMaxDepth)
    }
  }

  private def incrementalThreadDump(stackMaxDepth: Int): Option[ServiceThreads] = { // Java 5
    for (
      th <- threading;
      ids <- th.get[Array[Long]]("AllThreadIds");
      infos <- th.call[Array[CompositeData]]("getThreadInfo", ids, stackMaxDepth)
    ) yield {
      val threads =
        infos.filterNot(_ == null)
          .filter(_.get("threadId") != null)
          .map(ThreadInfo(serviceName, _))
      ServiceThreads(serviceName, threads)
    }
  }

  private def simultaneousThreadDump(stackMaxDepth: Int): Option[ServiceThreads] = { // Starting from Java 6
    for (th <- threading; infos <- th.call[Array[CompositeData]]("dumpAllThreads", true, true)) yield {
      val threads = infos.map(ThreadInfo(serviceName, _))
      ServiceThreads(serviceName, threads)
    }
  }

  lazy val javaSpecificationVersion: Option[String] = systemProperties.get("java.specification.version") // 1.5, 1.6, ...
  lazy val javaHome: Option[String] = systemProperties.get("java.home")
  lazy val userHome: Option[String] = systemProperties.get("user.home")
  lazy val userName: Option[String] = systemProperties.get("user.name")

  lazy val systemProperties = {
    var sysprops = Map.empty[String, String]
    for (
      rt <- runtime;
      props: TabularDataSupport <- rt.get("SystemProperties");
      (key, values) <- props.content;
      value <- values.get("value")
    ) { sysprops += key -> value.toString }
    sysprops
  }
  protected def buildOperationSignature(args: Array[Any]) =
    // http://home.pacifier.com/~mmead/jni/cs510ajp/index.html  /   http://en.wikipedia.org/wiki/Java_Native_Interface#Mapping_types
    args map {
      case _: Int => "int"
      case _: Short => "short"
      case _: Long => "long"
      case _: Float => "float"
      case _: Double => "double"
      case _: Boolean => "boolean"
      case _: Byte => "byte"
      case _: Char => "char"
      case _: String => "String"
      case _: Array[Int] => "[I"
      case _: Array[Short] => "[S"
      case _: Array[Long] => "[J"
      case _: Array[Float] => "[F"
      case _: Array[Double] => "[D"
      case _: Array[Boolean] => "[Z"
      case _: Array[Byte] => "[B"
      case _: Array[Char] => "[C"
      case _: Array[String] => "[Ljava/lang/String"
    }
}

private class JMXrmiImpl(rmiConnection: RMIConnection, val options: Option[JMXOptions] = None) extends JMX {

  def close() = try {
    rmiConnection.close
  } catch {
    case e => println("Exception while closing rmi connection, let's ignore and continue...")
  }

  private def mbeanInfoGetter(objectName: ObjectName) = rmiConnection.getMBeanInfo(objectName, null)
  private def attributeGetter(objectName: ObjectName, attrname: String) = Option(rmiConnection.getAttribute(objectName, attrname, null))
  private def attributeSetter(objectName: ObjectName, attrname: String, attrvalue: Any) {
    val attribute = new javax.management.Attribute(attrname, attrvalue)
    rmiConnection.setAttribute(objectName, attribute, null)
  }
  private def operationCaller(objectName: ObjectName, operationName: String, args: Array[Any]): Option[Any] = {
    Option(rmiConnection.invoke(objectName, operationName, args, buildOperationSignature(args), null))
  }
  private def newMBean(objectName: ObjectName) =
    RichMBean(
      objectName,
      () => mbeanInfoGetter(objectName),
      (attrname) => attributeGetter(objectName, attrname),
      (attrname, attrval) => attributeSetter(objectName, attrname, attrval),
      (operationName, args) => operationCaller(objectName, operationName, args)
    )

  def domains: List[String] = rmiConnection.getDomains(null).toList
  def exists(name: String): Boolean = rmiConnection.queryNames(name, null, null).size > 0
  def apply(name: String): RichMBean = newMBean(name)
  def mbeans(query: String): List[RichMBean] = rmiConnection.queryNames(null, string2objectName(query), null).toList map { newMBean(_) }
  def mbeans(): List[RichMBean] = rmiConnection.queryNames(null, null, null).toList map { newMBean(_) }
}

private class JMXclassicalImpl(conn: JMXConnector, val options: Option[JMXOptions] = None) extends JMX {
  private lazy val mbsc: MBeanServerConnection = conn.getMBeanServerConnection

  def close() = try {
    conn.close()
  } catch {
    case e => println("Exception while closing rmi connection, let's ignore and continue...")
  }

  private def mbeanInfoGetter(objectName: ObjectName) = mbsc.getMBeanInfo(objectName)
  private def attributeGetter(objectName: ObjectName, attrname: String) = Option(mbsc.getAttribute(objectName, attrname))
  private def attributeSetter(objectName: ObjectName, attrname: String, attrvalue: Any) {
    val attribute = new javax.management.Attribute(attrname, attrvalue)
    mbsc.setAttribute(objectName, attribute)
  }
  private def operationCaller(objectName: ObjectName, operationName: String, args: Array[Any]): Option[Any] = {
    Option(mbsc.invoke(objectName, operationName, args.map(_.asInstanceOf[Object]), buildOperationSignature(args)))
  }
  private def newMBean(objectName: ObjectName) =
    RichMBean(
      objectName,
      () => mbeanInfoGetter(objectName),
      (attrname) => attributeGetter(objectName, attrname),
      (attrname, attrval) => attributeSetter(objectName, attrname, attrval),
      (operationName, args) => operationCaller(objectName, operationName, args)
    )

  def domains: List[String] = mbsc.getDomains().toList
  def exists(name: String): Boolean = mbsc.queryNames(name, null).size > 0
  def apply(name: String): RichMBean = newMBean(name)
  def mbeans(query: String): List[RichMBean] = mbsc.queryNames(null, string2objectName(query)).toList map { newMBean(_) }
  def mbeans(): List[RichMBean] = mbsc.queryNames(null, null).toList map { newMBean(_) }
}

object JMX {
  def register(ob: Object, obname: ObjectName) = ManagementFactory.getPlatformMBeanServer.registerMBean(ob, obname)

  def usingJMX[T <: { def close() }, R](resource: T)(block: T => R) = {
    try block(resource)
    finally resource.close
  }

  def once[R](
    host: String,
    port: Int,
    username: Option[String] = None,
    password: Option[String] = None)(block: JMX => R): R = {
    once[R](JMXOptions(host = host, port = port, username = username, password = password))(block)
  }

  def once[R]()(block: JMX => R) = once[R](None)(block)
  def once[R](options: JMXOptions)(block: JMX => R) = once[R](Some(options))(block)
  def once[R](someOptions: Option[JMXOptions])(block: JMX => R) = usingJMX(getImpl(someOptions)) { block(_) }

  def apply(options: JMXOptions) = getImpl(Some(options))
  def apply(options: Option[JMXOptions]) = getImpl(options)
  //def apply(host: String, port: Int) = getImpl(Some(JMXOptions(host = host, port = port)))
  def apply(host: String, port: Int, username:Option[String]=None, password:Option[String]=None) = getImpl(Some(JMXOptions(host = host, port = port, username=username, password=password)))
  def apply() = getImpl()

  // For jboss "jboss-client.jar" is mandatory
  def jbossServiceURL(host: String, port: Int = 9999) = "service:jmx:remoting-jmx://%s:%d".format(host, port)
  def jonasServiceURL(host: String, port: Int = 1099, name: String = "jonas") = "service:jmx:rmi:///jndi/rmi://%s:%d/jrmpconnector_%s".format(host, port, name)
  def jsr160ServiceURL(host: String, port: Int = 2500) = "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi".format(host, port)

  private def getImpl(options: Option[JMXOptions] = None): JMX = {
    options match {
      case None => getLocalImpl() // For quick test or evaluation purposes
      case Some(cfg) =>
        jmxLookup(cfg) getOrElse {
          throw new RuntimeException("Couldn't find any jmx connector for %s".format(cfg.toString))
        }
    }
  }

  private def jmxLookup(opt: JMXOptions): Option[JMX] = {
    getMBeanServerFromKnownJMXServiceUrl(opt) orElse {
      JMX.findMBeanServers(opt.host, opt.port, opt.credentials)
        .headOption
        .map(new JMXrmiImpl(_, Some(opt)))
    }
  }

  private def getMBeanServerFromKnownJMXServiceUrl(opt: JMXOptions): Option[JMX] = {
    opt.url map { u => new JMXclassicalImpl(jmxurl2connector(u, opt.credentials), Some(opt)) } orElse {
      val urls = Stream(
        jsr160ServiceURL(opt.host, opt.port),
        jonasServiceURL(opt.host, opt.port, opt.name.getOrElse("jonas")),
        jbossServiceURL(opt.host, opt.port)
      ).map(new JMXServiceURL(_))
      urls.find(jmxurlAlive(_, opt.credentials))
        .map(jmxurl2connector(_, opt.credentials))
        .map(new JMXclassicalImpl(_, Some(opt)))
    }
  }

  def jmxurl2connector(url: JMXServiceURL, credentialsopt: Option[Credentials] = None): JMXConnector = {
    //println(credentialsopt)
    credentialsopt match {
      case None => JMXConnectorFactory.connect(url)
      case Some(credentials) =>
        val env = new java.util.HashMap[String, Any]()
        env.put(JMXConnector.CREDENTIALS, Array[String](credentials.username, credentials.password))
        JMXConnectorFactory.connect(url, env)
    }
  }

  def jmxurlAlive(url: JMXServiceURL, credentialsopt: Option[Credentials]): Boolean = {
    val result = try {
      val connector = jmxurl2connector(url, credentialsopt)
      var jmx: Option[JMX] = None
      try {
        jmx = Some(new JMXclassicalImpl(connector))
        jmx.map(_.domains.size >= 0) getOrElse false
      } catch {
        case _ => false
      } finally {
        jmx.foreach(_.close)
      }
    } catch {
      case _ => false
    }
    //println(url.toString + " ==> " + result)

    result
  }

  private def getLocalImpl(): JMX = {
    val (jmxurl, chosenPort) = JMX.jmxLocalInit()
    println("WARNING : No JMX options given, automatic self connect done (%s)".format(jmxurl.toString))
    new JMXclassicalImpl(jmxurl2connector(jmxurl))
  }

  def checkServiceURL(urlstr: String): String = {
    val url = new JMXServiceURL(urlstr)
    var jmxc: Option[JMXConnector] = None
    try {
      jmxc = Some(JMXConnectorFactory.connect(url))
      val mbsc = jmxc.get.getMBeanServerConnection()
      val domains = mbsc.getDomains()
    } finally { jmxc.foreach(_.close) }
    urlstr
  }

  // Self init procedure, of course more for tests or eval purposes 
  def jmxLocalInit(): Tuple2[JMXServiceURL, Int] = {
    // TODO : Not safe...
    val csf = RMISocketFactory.getDefaultSocketFactory()
    var chosenPort: Option[Int] = None
    val ssf = new RMIServerSocketFactory {
      def createServerSocket(port: Int): ServerSocket = {
        val ssock = ServerSocketFactory.getDefault().createServerSocket(port)
        chosenPort = Some(ssock.getLocalPort())
        ssock
      }
    }
    LocateRegistry.createRegistry(0, csf, ssf)
    val url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:%d/jmxapitestrmi".format(chosenPort.get))
    val mbs = ManagementFactory.getPlatformMBeanServer()
    val cs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs)
    cs.start()
    Thread.sleep(500) // TODO : To remove, how to know when everything is ready ?
    url -> chosenPort.get
  }

  def findMBeanServers(host: String, port: Int, credentials: Option[Credentials]): List[RMIConnection] = {
    def searchMissingClass(e: Throwable): String = {
      e match {
        case null => "?"
        case ex: ClassNotFoundException => ex.getMessage().trim().split("\\s+")(0)
        case _ => searchMissingClass(e.getCause)
      }
    }
    try {
      val lr = java.rmi.registry.LocateRegistry.getRegistry(host, port)
      //filterNot => some jonas MBeans causes registry data corruption if lookuped then all others call are in exception
      val entries = lr.list.toList
        .filterNot(_ contains "TMFactory")
        .filterNot(_ contains "RMI_SERVER_RPC")
      entries map { entry =>
        try {
          val credentialsArray: Array[String] = credentials match {
            case None => Array()
            case Some(Credentials(username, password)) => Array(username, password)
          }
          lr.lookup(entry) match {
            case stub: RMIServerImpl_Stub => Some(stub.newClient(credentialsArray))
            case rsi: RMIServer => Some(rsi.newClient(credentialsArray))
            case r @ _ => println("Do not know how to manage %s class %s".format(entry, r.getClass)); None
          }
        } catch {
          case e: UnmarshalException => /*println("%s - Missing = %s".format(entry, searchMissingClass(e)));*/ None
        }
      } flatten
    }
  }
}
